﻿using System;
using System.Collections.Generic;
using Notoric.Commanding.CommandBase;

namespace Notoric.ViewModel
{
    /// <summary>
    /// An undoable command for inserting new measures into a composition.
    /// </summary>
    /// <remarks>
    /// Input parameters:
    /// <list type="table">
    /// <listheader>
    /// <term>
    ///     Name
    /// </term>
    /// <description>
    ///     Description
    /// </description>
    /// </listheader>
    /// <item>
    /// <term>
    ///     <see cref="Coda"/> (<see cref="Boolean"/>)
    /// </term>
    /// <description>
    ///     Indicates whether the measures will be inserted to the coda
    ///     or the measures collection.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     <see cref="Composition"/> (<see cref="Elements.Composition"/>)
    /// </term>
    /// <description>
    ///     The composition into which the measures will be inserted.
    /// </description>
    /// </item>
    /// <item>
    /// <term>
    ///     <see cref="Start"/> (<see cref="UInt16"/>)
    /// </term>
    /// <description>
    ///     The number (1-based index) of the first inserted measure.
    ///     The default value is 0 meaning that the measures will be inserted
    ///     to the end of the composition.
    /// </description>
    /// </item>
    /// </list>
    /// </remarks>
    public class InsertMeasuresCommand : UndoableCommandBase
    {
        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="InsertMeasuresCommand"/> class.
        /// </summary>
        /// <param name="undoStack">The undo command stack</param>
        /// <param name="redoStack">The redo command stack</param>
        public InsertMeasuresCommand(CommandStack undoStack, CommandStack redoStack)
            : base(undoStack, redoStack)
        {
            AddParameter<bool>("Coda", ParameterDirection.In);
            AddParameter<Elements.Composition>("Composition", ParameterDirection.In);
            AddParameter<int>("Count", ParameterDirection.In);
            AddParameter<ushort>("Start", ParameterDirection.In);

            Start = 0;
            Name = "Insert Measures";
            measures = new List<Model.Elements.MeasureElement>();
        }

        #endregion

        #region Command Logic

        /// <inheritdoc/>
        protected override void CommandOperation(object parameter)
        {
            var modelComposition = Composition.AdaptedElement as Model.Elements.CompositionElement;
            if (Start == 0)
            {
                if (Coda)
                    Start = (ushort)(modelComposition.MeasuresCount + 1);
                else
                    Start = (ushort)(modelComposition.Measures.Count + 1);
            }

            for (int i = 0; i < Count; ++i)
            {
                var measure = modelComposition.CreateMeasure();

                if (Coda)
                    modelComposition.Coda.Insert(Start - modelComposition.Measures.Count + i - 1, measure);
                else
                    modelComposition.Measures.Insert(Start + i - 1, measure);

                measures.Add(measure);
            }

            Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(
                ((Elements.CWNotation.MusicSectionInterior)Composition.LinearView.Content.Interior).Systems[0].AdaptedElement as Model.Elements.CWNotation.System
                );

            Composition.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void RedoOperation()
        {
            var modelComposition = Composition.AdaptedElement as Model.Elements.CompositionElement;

            for (int i = 0; i < Count; ++i)
            {
                if (Coda)
                    modelComposition.Coda.Insert(Start - modelComposition.Measures.Count + i - 1, measures[i]);
                else
                    modelComposition.Measures.Insert(Start + i - 1, measures[i]);
            }

            Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(
                ((Elements.CWNotation.MusicSectionInterior)Composition.LinearView.Content.Interior).Systems[0].AdaptedElement as Model.Elements.CWNotation.System
                );
            
            Composition.FindOwner<Elements.Project>().IsModified = true;
        }

        /// <inheritdoc/>
        protected override void UndoOperation()
        {
            var modelComposition = Composition.AdaptedElement as Model.Elements.CompositionElement;

            if (Coda)
            {
                for (int i = Start + Count - 2 - modelComposition.Measures.Count; i >= Start - modelComposition.Measures.Count - 1; --i)
                    modelComposition.Coda.RemoveAt(i);
            }
            else
            {
                for (int i = Start + Count - 2; i >= Start - 1; --i)
                    modelComposition.Measures.RemoveAt(i);
            }

            Model.Elements.CWNotation.Layout.Layouter.LayoutHorizontallyNA(
                ((Elements.CWNotation.MusicSectionInterior)Composition.LinearView.Content.Interior).Systems[0].AdaptedElement as Model.Elements.CWNotation.System
                );

            Composition.FindOwner<Elements.Project>().IsModified = true;
        }

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets a value indicating whether the inserted measures belong to the coda.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Boolean"/>
        /// </para>
        /// <para>
        /// <see langword="true"/> if the measures will be inserted 
        /// to the coda collection. Otherwise they will be inserted
        /// to the measures collection.<br/>
        /// This property wraps the "Coda" command parameter.
        /// </para>
        /// </value>
        public bool Coda
        {
            get { return GetParameterValue<bool>("Coda"); }
            set { SetParameterValue<bool>("Coda", value); }
        }

        /// <summary>
        /// Gets or sets the composition to which the measures will be inserted.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Elements.Composition"/>
        /// </para>
        /// <para>
        /// The composition to which the measures will be inserted.<br/>
        /// This property wraps the "Composition" command parameter.
        /// </para>
        /// </value>
        public Elements.Composition Composition
        {
            get { return GetParameterValue<Elements.Composition>("Composition"); }
            set { SetParameterValue<Elements.Composition>("Composition", value); }
        }

        /// <summary>
        /// Gets or sets the count of created measures.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="Int32"/>
        /// </para>
        /// <para>
        /// The count of created measures.<br/>
        /// This property wraps the "Count" command parameter.
        /// </para>
        /// </value>
        public int Count
        {
            get { return GetParameterValue<int>("Count"); }
            set { SetParameterValue<int>("Count", value); }
        }

        /// <summary>
        /// Gets or sets the number of the first inserted measure.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="UInt16"/>
        /// </para>
        /// <para>
        /// The number (1-based index) of the first inserted measure.
        /// This value can be 0 in which case the measures will be inserted
        /// to the end of the measures collection. The default value is 0.<br/>
        /// This property wraps the "Start" command parameter.
        /// </para>
        /// </value>
        public ushort Start
        {
            get { return GetParameterValue<ushort>("Start"); }
            set { SetParameterValue<ushort>("Start", value); }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the list of created measures.
        /// </summary>
        private List<Model.Elements.MeasureElement> measures;

        #endregion
    }
}
